#include <mips/regdef.h>
#include <sys/syscall.h>

#define INFD	0
#define OUTFD	4
#define OPTS	8
#define CH_ACT	16
#define LG_ACT	20
#define LG_DEL	24
#define OCTAL	28
#define O_GP	32
#define O_FP	36
#define O_RA	40
#define STACK_SIZE	44

	.text						// Lo situa en la sección de código de la memoria
	.align	2

	.globl	odump_encode
	.ent	odump_encode		// Instrumenta símbolo de debugging
/*	Stack Frame de odump_encode  (non-leaf function):
			----
	(SRA)  44  | PAD  |
	(SRA)  40  | RA   |	>> Dirección de retorno
	(SRA)  36  | FP   |	>> Frame Pointer
	(SRA)  32  | GP   |	>> Global Pointer

	(LTA)  28  | VAR4 |	>> digito octal a imprimir
	(LTA)  24  | VAR3 |	>> longitud del delimitador
	(LTA)  20  | VAR2 |	>> contador de nros octales 'dumpeados' en la linea
	(LTA)  16  | VAR1 |	>> caracter actual

	(ABA)  12  | PAD  |
	(ABA)  8   | ARG2 |	>> *opts :	(0)size_t length, (4)const char* delim
	(ABA)  4   | ARG1 |	>> outfd
	(ABA)  0   | ARG0 |	>> infd
			----
*/
odump_encode:	// int odump_encode(int infd, int outfd, struct odump_opts* opts)
	.frame	$fp,STACK_SIZE,ra	// defino el tamaño de la Stack Frame
	.set	noreorder
	.cpload	t9			// t9 guarda la direccion de la funcion que se está ejecutando
	.set	reorder

// Se crea la Stack Frame
	subu		sp,sp,STACK_SIZE	// reservo STACK_SIZE bytes de espacio
// Guardo los registros en el SRA de la callee
	.cprestore  O_GP			// equivale a sw gp,O_GP(sp)
	sw	    	$fp, O_FP(sp)
	sw	    	ra, O_RA(sp)
	move		$fp, sp			// copio la dirección de sp en $fp. Ahora $fp apunta al principio del SF
// Guardo los argumentos en el ABA de la callee
	sw	    	a0, INFD($fp)
	sw	    	a1, OUTFD($fp)
	sw	    	a2, OPTS($fp)

// Calculo la longitud del delimitardor por unica vez
	lw	t0, OPTS($fp)	// cargo el puntero al struct odump_opts
	addiu	t0, t0, 4	// me seteo en donde está el puntero al delimitador
	lw	a0, 0(t0)
	jal	mystrlen		// calculo la longitud del delimitador
	sw	v0, LG_DEL($fp)	// la guardo

// Inicializaciones
	sw	zero, LG_ACT($fp)	// inicializo en 0 el contador de longitud de linea
	li	s1, 0			// inicializo en 0 el offset de lectura
	li	s2, 0			// inicializo en 0 el offset de escritura
	li	s3, 0			// inicializo en 0 el valor de retorno

odump_encode_while:
	bnez	s3, odump_encode_return	// si se produjo un error salgo del while

// Llamada al sistema para leer del stream de entrada
	li	v0, SYS_lseek	// move the read/write file offset
    lw	a0, INFD($fp)	// file descriptor
    li	a1, 0
    la	a2, 0(s1)		// offset respecto de a3
    li	a3, 0			// a3=0: comienzo del archivo
    syscall
	addiu	s1,	s1, 1	// incremento el offset de lectura

	li	v0, SYS_read 	// uso <sys/syscall.h>: ssize_t read(int fd, void *buffer, size_t cantidad);
    lw	a0, INFD($fp)   // a0: input file descriptor
    la	a1, CH_ACT($fp) // a1: puntero al caracter actual
    li	a2, 1         	// a2: lee de a uno
    syscall

// Examino el retorno de SYS_read (nro de bytes leidos)
	beqz	v0, odump_encode_return	// no leyo ninguno: se encontró el EOF
	bne		v0, 1, odump_encode_lectura_error	// leyo más de un byte

if_length:
	lw	t0, OPTS($fp)				// cargo el puntero al struct odump_opts
	lw	t1, 0(t0)					// cargo la longitud de linea desde el struct odump_opts
	beqz	t1, else_delimiter		// si la longitud de linea es 0, salto al else
	lw	t2, LG_ACT($fp)				// cargo la longitud de linea actual
	bltu	t2, t1, else_delimiter	// si la longitud actual es menor, salto al else

	li	v0, SYS_lseek	// move the read/write file offset
    lw	a0, OUTFD($fp)	// file descriptor
    li	a1, 0
    la	a2, 0(s2)		// offset respecto de a3
    li	a3, 0			// a3=0: comienzo del archivo
    syscall
	addiu	s2,	s2, 1	// incremento el offset de escritura

	li	v0, SYS_write  	// uso <sys/syscall.h>: ssize_t write(int fd, void *buffer, size_t cantidad);
	lw	a0, OUTFD($fp) 	// a0: output file descriptor
	la	a1, new_line	// a1: puntero al caracter de salto de linea
	li	a2, 1	       	// a2: cantidad a escribir (bytes)
	syscall

	sw	zero, LG_ACT($fp)	// pongo en 0 el contador de longitud de linea

	j codificar_e_imprimir

else_delimiter:
	lw	t0, LG_ACT($fp)					// cargo la longitud de linea actual
	beqz	t0, codificar_e_imprimir	// si la longitud actual es 0, no entro al else

	lw	s0,	LG_DEL($fp)	// cargo en s0 la longitud del delimitador

	li	v0, SYS_lseek	// move the read/write file offset
    lw	a0, OUTFD($fp)	// file descriptor
    li	a1, 0
    la	a2, 0(s2)		// offset respecto de a3
    li	a3, 0			// a3=0: comienzo del archivo
    syscall
	addu	s2,	s2, s0	// incremento el offset de escritura

	lw	t0, OPTS($fp)	// cargo el puntero al struct odump_opts
	addiu	t0, t0, 4	// me seteo en donde está el puntero al delimitador
	lw	t1, 0(t0)

	li	v0, SYS_write  	// uso <sys/syscall.h>: ssize_t write(int fd, void *buffer, size_t cantidad);
	lw	a0, OUTFD($fp) 	// a0: output file descriptor
	la	a1, 0(t1)		// a1: puntero al delimitador
	move	a2, s0     	// a2: cantidad a escribir (bytes)
	syscall

codificar_e_imprimir:

	li	s0,	2		// inicializo el contador de digitos octales
while_codificar:
	bltz	s0, fin_while_codificar
// Se realiza la codificación a octal:
	mulou	t1, s0, 3	// calculo la cantidad de bits a desplazar
	lbu	t0, CH_ACT($fp)	// cargo el caracter
	srl	t0, t0, t1		// desplazo t1 bits a derecha
	andi	t0, t0, 7	// conservo los primeros 3 bits

// Transformo el entero a el caracter correspondiente
	addu	t2, t0,	48	// le sumo el ascii del '0'
	sw	t2, OCTAL($fp)

// Llamada al sistema para imprimir en el stream de salida
	li	v0, SYS_lseek	// move the read/write file offset
	lw	a0, OUTFD($fp)	// file descriptor
	li	a1, 0
	la	a2, 0(s2)		// offset respecto de a3
	li	a3, 0			// a3=0: comienzo del archivo
    syscall
	addiu	s2,	s2, 1	// incremento el offset de escritura

	li	v0, SYS_write  	// uso <sys/syscall.h>: ssize_t write(int fd, void *buffer, size_t cantidad);
	lw	a0, OUTFD($fp) 	// a0: output file descriptor
	la	a1, OCTAL($fp)	// a1: puntero al digito octal
	li	a2, 1	       	// a2: cantidad a escribir (bytes)
	syscall
	bne		v0, 1, odump_encode_escritura_error	// escribió más de un byte

	subu	s0,	s0, 1

	j	while_codificar

fin_while_codificar:
// Incremento el contador de cantidad de bytes dumpeados
	lw	t0, LG_ACT($fp)
	addiu	t0, t0, 1
	sw	t0,	LG_ACT($fp)

	j	odump_encode_while

odump_encode_escritura_error:
	li	s0, 0	// indice del mensaje de error
	li	s3, 2	// tipo de error: FATAL ERROR
	b	odump_decode_error

odump_encode_lectura_error:
	li	s0, 1	// indice del mensaje de error
	li	s3, 2	// tipo de error: FATAL ERROR
	b	odump_decode_error

odump_encode_error:
	la	s1, odump_errmsg
	sll	s0, s0, 2
	addu	s1, s1, s0

// Calculo la longitud del mensaje de error
	lw	a0, 0(s1)
	jal	mystrlen

	li	a0, 2 			// a0: STDERR
	lw	a1, 0(s1)		// a1: puntero al mensaje de error
	move	a2, v0		// a2: longitud del mensaje
// Imprimo el mensaje de error
	li	v0, SYS_write  	// uso <sys/syscall.h>: ssize_t write(int fd, void *buffer, size_t cantidad);
	syscall

odump_encode_return:
// Cargo los registros del SRA de la callee
	lw 	ra,O_RA(sp)
	lw 	$fp,O_FP(sp)
	lw 	gp,O_GP(sp)
// Se destruye la Stack Frame: desapilo
	addiu 	sp,sp,STACK_SIZE
// Retorno
	move	v0, s3	// copio el valor de retorno al registro correspondiente
	jr ra
	.end	odump_encode

/**********************************************************************/
#define DG_OCT	16
#define DEC_CH	20
//#define LG_DEL	24

	.globl	odump_decode
	.ent	odump_decode		// Instrumenta símbolo de debugging
/*	Stack Frame de odump_decode  (non-leaf function):
			----
	(SRA)  44  | PAD  |
	(SRA)  40  | RA   |	>> Dirección de retorno
	(SRA)  36  | FP   |	>> Frame Pointer
	(SRA)  32  | GP   |	>> Global Pointer

	(LTA)  28  | PAD  |
	(LTA)  24  | VAR3 |	>> longitud del delimitador
	(LTA)  20  | VAR2 |	>> caracter decodificado
	(LTA)  16  | VAR1 |	>> digito octal leido

	(ABA)  12  | PAD  |
	(ABA)  8   | ARG2 |	>> *opts :	(0)size_t length, (4)const char* delim
	(ABA)  4   | ARG1 |	>> outfd
	(ABA)  0   | ARG0 |	>> infd
			----
*/
odump_decode:	// int odump_decode(int infd, int outfd, struct odump_opts* opts)
	.frame	$fp,STACK_SIZE,ra	// defino el tamaño de la Stack Frame
	.set	noreorder
	.cpload	t9			// t9 guarda la direccion de la funcion que se está ejecutando
	.set	reorder

// Se crea la Stack Frame
	subu		sp,sp,STACK_SIZE	// reservo STACK_SIZE bytes de espacio
// Guardo los registros en el SRA de la callee
	.cprestore  O_GP			// equivale a sw gp,O_GP(sp)
	sw	    	$fp, O_FP(sp)
	sw	    	ra, O_RA(sp)
	move		$fp, sp			// copio la dirección de sp en $fp. Ahora $fp apunta al principio del SF
// Guardo los argumentos en el ABA de la callee
	sw	    	a0, INFD($fp)
	sw	    	a1, OUTFD($fp)
	sw	    	a2, OPTS($fp)

// Calculo la longitud del delimitardor por unica vez
	lw	t0, OPTS($fp)	// cargo el puntero al struct odump_opts
	addiu	t0, t0, 4	// me seteo en donde está el puntero al delimitador
	lw	a0, 0(t0)
	jal	mystrlen		// calculo la longitud del delimitador
	sw	v0, LG_DEL($fp)	// la guardo

// Inicializaciones
	li	s1, 0			// inicializo en 0 el offset de lectura
	li	s2, 0			// inicializo en 0 el offset de escritura
	li	s3, 0			// inicializo en 0 el valor de retorno

odump_decode_while:
	bnez	s3, odump_decode_return	// si se produjo un error salgo del while

	li	s0,	2				// inicializo el contador de digitos octales
	sw	zero, DEC_CH($fp)	// inicializo en 0 el caracter decodificado
while_decodificar:
	bltz	s0, fin_while_decodificar

// Llamada al sistema para leer del stream de entrada
	li	v0, SYS_lseek	// move the read/write file offset
    lw	a0, INFD($fp)	// file descriptor
    li	a1, 0
    la	a2, 0(s1)		// offset respecto de a3
    li	a3, 0			// a3=0: comienzo del archivo
    syscall
	addiu	s1,	s1, 1	// incremento el offset de lectura

	li	v0, SYS_read 	// uso <sys/syscall.h>: ssize_t read(int fd, void *buffer, size_t cantidad);
    lw	a0, INFD($fp)   // a0: input file descriptor
    la	a1, DG_OCT($fp) // a1: digito octal (char)
    li	a2, 1         	// a2: lee de a uno
    syscall

// Examino el retorno de SYS_read (nro de bytes leidos)
	beqz	v0, odump_decode_return	// no leyo ninguno: se encontró el EOF
	bne		v0, 1, odump_decode_lectura_error	// leyo más de un byte

char_to_digit:
// Intento transformar el caracter a un digito octal
	lbu	t2, DG_OCT($fp)
	subu	t2, t2, 48		// le resto el ascii de '0' para quedarme con el entero
// Decido si es un caracter octal o no
	bgt	t2, 7, not_an_octal_digit
	bltz	t2, not_an_octal_digit
	b print_char

not_an_octal_digit:
// Verifico que el caracter extraño no sea parte de los 3 digitos octales
	bne	s0, 2, no_es_digito_error

// Me fijo si es un salto de linea
	lbu	t0, new_line
	lbu	t1, DG_OCT($fp)
	bne	t0, t1, is_delimiter
	j	odump_decode_while

is_delimiter:
// Incremento el offset de lectura según longitud del delimitador
	lw	t0,	LG_DEL($fp)
	subu	t0, t0, 1
	addu	s1,	s1, t0
	j	odump_decode_while

print_char:
	mulou	t1, s0, 3	// calculo la cantidad de bits a desplazar
	sll	t2, t2, t1		// desplazo t1 bits a izquierda

	lbu	t0, DEC_CH($fp)
	or	t0, t2, t0
	sb	t0, DEC_CH($fp)

	subu	s0,	s0, 1

	j	while_decodificar

fin_while_decodificar:
// Llamada al sistema para imprimir en el stream de salida
	li	v0, SYS_lseek	// move the read/write file offset
	lw	a0, OUTFD($fp)	// file descriptor
	li	a1, 0
	la	a2, 0(s2)		// offset respecto de a3
	li	a3, 0			// a3=0: comienzo del archivo
    syscall
	addiu	s2,	s2, 1	// incremento el offset de escritura

	li	v0, SYS_write  	// uso <sys/syscall.h>: ssize_t write(int fd, void *buffer, size_t cantidad);
	lw	a0, OUTFD($fp) 	// a0: output file descriptor
	la	a1, DEC_CH($fp)	// a1: puntero al caracter decodificado
	li	a2, 1	       	// a2: cantidad a escribir (bytes)
	syscall
	bne		v0, 1, odump_decode_escritura_error	// escribió más de un byte

	j	odump_decode_while

odump_decode_escritura_error:
	li	s0, 0	// indice del mensaje de error
	li	s3, 2	// tipo de error: FATAL ERROR
	b	odump_decode_error

odump_decode_lectura_error:
	li	s0, 1	// indice del mensaje de error
	li	s3, 2	// tipo de error: FATAL ERROR
	b	odump_decode_error

no_es_digito_error:
	li	s0, 2	// indice del mensaje de error
	li	s3, 1	// tipo de error: ABORT
	b	odump_decode_error

odump_decode_error:
	la	s1, odump_errmsg
	sll	s0, s0, 2
	addu	s1, s1, s0

// Calculo la longitud del mensaje de error
	lw	a0, 0(s1)
	jal	mystrlen

	li	a0, 2 			// a0: STDERR
	lw	a1, 0(s1)		// a1: puntero al mensaje de error
	move	a2, v0		// a2: longitud del mensaje
// Imprimo el mensaje de error
	li	v0, SYS_write  	// uso <sys/syscall.h>: ssize_t write(int fd, void *buffer, size_t cantidad);
	syscall

odump_decode_return:
// Cargo los registros del SRA de la callee
	lw 	ra,O_RA(sp)
	lw 	$fp,O_FP(sp)
	lw 	gp,O_GP(sp)
// Se destruye la Stack Frame: desapilo
	addiu 	sp,sp,STACK_SIZE
// Retorno
	move	v0, s3	// copio el valor de retorno al registro correspondiente
	jr ra
	.end	odump_decode

/**********************************************************************/

	.ent	mystrlen
/*	Stack Frame de mystrlen (leaf function):
			----
	(SRA)  12 | PAD |
	(SRA)  8  | PAD |
	(SRA)  4  | FP  |	>> Frame Pointer
	(SRA)  0  | GP  |	>> Global Pointer
			----
*/
mystrlen:	// int mystrlen(char* str)
	.frame	$fp, 16, ra
	.set	noreorder
	.cpload	t9
	.set	reorder
	subu	sp, sp, 16
// Guardo los registros en el SRA de la callee
	.cprestore 0
	sw	$fp, 4(sp)
	move	$fp, sp

	li	v0, 0		// inicializo el valor de retorno
mystrlen_loop:
	lb	t0, 0(a0)
	beqz	t0, mystrlen_return
	addiu	a0, a0, 1
	addiu	v0, v0, 1
	j	mystrlen_loop

mystrlen_return:
// Destruimos el frame
	lw		$fp, 4(sp)
	addu	sp, sp, 16
// Retorno
	j	ra
	.end	mystrlen

	.rdata	// segmento read-only data
	.align 2
odump_errmsg:
	.word	err_write, err_read, err_dig
	.size	odump_errmsg, 8

	.align	0
err_write:	.asciiz "Error de escritura en el stream de salida\n"
err_read:	.asciiz "Error de lectura en el stream de entrada\n"
err_dig:	.asciiz "Entrada invalida: se esperaba un nro octal\n"

new_line:	.asciiz "\n"
